package free.akerigan.yafb2lib.book;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import free.akerigan.yafb2lib.library.LibraryModel;
import free.akerigan.yafb2lib.model.AuthorModel;
import free.akerigan.yafb2lib.model.BookModel;
import free.akerigan.yafb2lib.model.SequenceModel;

public class FB2Parser extends DefaultHandler {

    protected BookModel bookInfo = null;
    protected ArrayList<AuthorModel> authors = null;
    protected AuthorModel currentAuthor = null;
    protected boolean store = false;
    protected StringBuilder buffer = null;
    protected LinkedList<String> tags = null;
    protected SequenceModel sequence = null;

    protected static HashMap<String, String> bookInfoMapping;
    protected static HashMap<String, String> authorInfoMapping;

    static {
	bookInfoMapping = new HashMap<String, String>();
	bookInfoMapping.put("fictionbook/description/title-info/book-title",
		"setTitle");
	bookInfoMapping.put("fictionbook/description/title-info/annotation",
		"setAnnotation");
	bookInfoMapping.put("fictionbook/description/title-info/genre",
		"setGenre");
	bookInfoMapping.put("fictionbook/description/title-info/date",
		"setDate");

	authorInfoMapping = new HashMap<String, String>();
	authorInfoMapping.put(
		"fictionbook/description/title-info/author/first-name",
		"setFirstName");
	authorInfoMapping.put(
		"fictionbook/description/title-info/author/middle-name",
		"setMiddleName");
	authorInfoMapping.put(
		"fictionbook/description/title-info/author/last-name",
		"setLastName");
    }

    public FB2Parser() {
    }

    @Override
    public void startDocument() throws SAXException {
	bookInfo = new BookModel();
	authors = new ArrayList<AuthorModel>();
	store = false;
	buffer = new StringBuilder();
	tags = new LinkedList<String>();
	sequence = new SequenceModel();
    }

    @Override
    public void startElement(String uri, String localName, String name,
	    Attributes attributes) throws SAXException {
	tags.add(name.toLowerCase());
	String path = getPath();
	if (bookInfoMapping.containsKey(path)) {
	    store = true;
	} else if (path.equals("fictionbook/description/title-info/author")) {
	    currentAuthor = new AuthorModel();
	} else if (authorInfoMapping.containsKey(path)) {
	    store = true;
	} else if (path.equals("fictionbook/description/title-info/sequence")) {
	    sequence.setName(attributes.getValue("name"));
	    sequence.setNumber(Integer.parseInt(attributes.getValue("number")));
	} else {
	    if (store) {
		buffer.append("<" + name + ">");
	    }
	}
    }

    @Override
    public void endElement(String uri, String localName, String name)
	    throws SAXException {

	String path = getPath();

	if (bookInfoMapping.containsKey(path)) {
	    store = false;
	    setInfo(bookInfo, bookInfoMapping.get(path), buffer.toString());
	    clearBuffer();
	} else if (path.equals("fictionbook/description/title-info/author")) {
	    authors.add(currentAuthor);
	    currentAuthor = null;
	} else if (authorInfoMapping.containsKey(path)) {
	    store = false;
	    setInfo(currentAuthor, authorInfoMapping.get(path), buffer
		    .toString());
	    clearBuffer();
	} else {
	    if (store) {
		buffer.append("</" + name + ">");
	    }
	}
	tags.remove(tags.size() - 1);
    }

    @Override
    public void characters(char[] ch, int start, int length)
	    throws SAXException {
	if (store) {
	    buffer.append(new String(ch, start, length));
	}
    }

    protected void clearBuffer() {
	buffer.delete(0, buffer.length());
    }

    protected String getPath() {
	boolean notFirst = false;
	StringBuilder path = new StringBuilder();
	for (String tag : tags) {
	    if (notFirst) {
		path.append("/");
		path.append(tag);
	    } else {
		path.append(tag);
		notFirst = true;
	    }
	}
	return path.toString();
    }

    @SuppressWarnings("unchecked")
    protected void setInfo(Object infoModel, String methodName, String value) {
	try {
	    Class cls = infoModel.getClass();
	    Class partypes[] = new Class[1];
	    partypes[0] = String.class;
	    Method meth = cls.getMethod(methodName, partypes);
	    Object arglist[] = new Object[1];
	    arglist[0] = value;
	    meth.invoke(infoModel, arglist);
	} catch (Exception e) {
	    e.printStackTrace();
	}
    }

    public BookModel getBookModel() {
	return bookInfo;
    }

    public ArrayList<AuthorModel> getAuthors() {
	return authors;
    }

    public static void parse(InputStream in, LibraryModel library) {
	FB2Parser parser = new FB2Parser();
	try {
	    SAXParserFactory spf = SAXParserFactory.newInstance();
	    SAXParser sp = spf.newSAXParser();
	    sp.parse(new InputSource(in), parser);
	} catch (Exception e) {
	    e.printStackTrace();
	    return;
	}
	BookModel bookModel = parser.getBookModel();
	int bookId = library.addBookModel(bookModel);
	for (AuthorModel author : parser.getAuthors()) {
	    author.addBook(bookId);
	    String authorId = library.addAuthor(author);
	    bookModel.addAuthor(authorId);
	}
    }

}
